home *** CD-ROM | disk | FTP | other *** search
- /* Copyright ©November 1993, Dale Schaafsma All rights reserved */
- /* main code section for xcmd that recognizes touch tones */
- /* parts taken from the following */
- /**********
- * Sound Confusion
- *
- * Copyright © 1992 Bernie Bernstein
- * 9/11/92 - 10/14/92
- *
- * updated 9/4/93
- ***********/
- /***********
- * Fast fourier code from Numerical Recipes in C (altered just slightly)
- **********/
- /* Thanks to the following for his contribution of the phone
- * frequencies: brie@brie.andrew.cmu.edu (Brian Harrison)
- *
- * "Dual Tone MultiFrequency"
- * use a monospace font for this table.
- * 1209 1336 1477 1633
- * 697 1 2 3 A
- * 770 4 5 6 B
- * 852 7 8 9 C
- * 941 * 0 # D
- */
- /* define the keypad on a typical phone */
- static char keypad[4][3] = {{'1','2','3'},{'4','5','6'},{'7','8','9'},{'#','0','*'}};
-
- /**********************
- * The values outputted by the fft were figured out by using the following
- * formula where f is the frequency of the partial tone, and x is the output of
- * the fft: x = 512*f/(22kHz/2)
- *******************/
- /* defining the numbers for the columns and rows (this is what the fft puts out,
- * more exactly these are integers corresponding to the elements of the array) */
- #define R1fft 33 /* adjusted from experimental data (orig. 32) */
- #define R2fft 36
- #define R3fft 40
- #define R4fft 45 /* adjusted from experimental data (orig. 44) */
- #define C1fft 57 /* adjusted from experimental data (orig. 56) */
- #define C2fft 62
- #define C3fft 69
- #define C4fft 76
- /* define the accuracy the fft has to satisfy to match */
- #define kFFTtolerance 0
-
- #include <types.h>
- #include <GestaltEqu.h>
- #include <OSEvents.h>
- #include <Memory.h>
- #include <Sound.h>
- #include <SoundInput.h>
- #include <math.h>
- #include <HyperXcmd.h>
-
- #include "record.h"
-
- /* declare the global variable */
- gGlobalType* gGlobal;
- /* extern the phScripts */
- extern StringPtr *phScripts[12];
-
-
- /***this function taken from soundConfusion by Bernie Bernstein
- * SetUpSounds
- * SetUpSounds is a routine which takes a buffer handle, sound headers size and
- * sample rate value and turns it into a snd buffer with the proper header.
- * It then returns the buffer handle back to the caller.
- ***/
- Handle SetUpSounds (Handle Buf, short *HeaderSize, Fixed sampRate)
- {
- OSErr err;
- short dummy;
-
- Buf = NewHandle(kBuffSize);
- if ((err = MemError()) != noErr || Buf == nil)
- return(nil);
- HLockHi (Buf);
- if ((err = MemError()) != noErr)
- return(nil);
-
- /* parameters are as follows: handle, channels, samplerate, samplesize(8bit),
- compression, baseline, size, header)
- size is the # of bytes to be stored (may not be the same as # of samples */
- /* so a more accurate comment is the following: setupsnd header is called twice,
- * the 1st time is to get the size of the header, the 2nd is to insert the size
- * into that header (so keep the 2 calls) */
- /* additional notes, the meter thing is an immediate status, the completion routine
- is called when count bytes are recorded or m milliseconds have passed, the int
- routine is called when the internal buffers of the snd chip are full (this is to
- do processing on the data prior to even getting it committed to main mem, most
- other stuff is as expected, there was no gestalt for play&record */
- /* The call to SetupSndHeader is done twice in order to make sure that the
- Header size is set correctly. */
-
- err = SetupSndHeader (Buf, 1, sampRate, 8, 'NONE', 0x3C, 0, HeaderSize);
- if (err != noErr)
- return(nil);
- err = SetupSndHeader (Buf, 1, sampRate, 8, 'NONE', 0x3C, kBuffSize - *HeaderSize, &dummy);
- if (err != noErr)
- return(nil);
-
- return (Buf);
- }
-
- /****************************************/
- /* findAvailableBuff:
- * Finds the next available buffer
- * returns: the number of it or -1 if there aren't any
- */
- int findAvailableBuff()
- {
- int i;
-
- for(i = 0; i < gGlobal->NumBuffers; i++)
- if (gGlobal->BuffArray[i].full == false)
- return(i);
- return(-1);
- }
-
- /****************************************/
- /* initToneRecognize:
- * initializes this whole thing
- * returns: an integer error code (defined in the .h file) -1 is no error
- */
- int initToneRecognize()
- {
- OSErr err;
- long feature;
- long SoundRefNum;
- Fixed SampleRate;
- int NumBuffers,i;
- BuffInfo* BuffArray;
- SPBPtr RecordRec;
-
- /* grow the application size to the maximum */
- /* MaxApplZone();*/
-
- /* allocate the global */
- gGlobal = (gGlobalType*)NewPtr(sizeof(gGlobalType));
- if (MemError() != noErr || gGlobal == nil)
- return(kMemory);
-
- /* use Gestalt manager to make sure sound input and output is possible */
- err = Gestalt(gestaltSoundAttr, &feature);
- if (err == noErr)
- {
- if (!TestBit(feature, gestaltHasSoundInputDevice))
- return(kNoInput);
- else
- {
-
- /* does the machine have stereo? */
- gGlobal->Stereo = TestBit(feature, gestaltStereoCapability);
-
- /* it was claimed that the play and record bit was created in
- /* system 7.1, but I couldn't find it. I used a program (Parrot
- /* by Bernie Bernstein) and it would record and play simultaneously,
- /* so I looked at the output of the gestalt call, and the only bits
- /* on were in bits 0 through 5, and as bits 0 through 5 are
- /* already defined in the gestalt header file (not as the
- /* playAndRecord bit) I am assuming that if we have stereo we
- /* have the better Sound Chip and so can Play and Record at the
- /* same time */
- gGlobal->PlayAndRecord = gGlobal->Stereo;
- }
- }
- else
- return(kGestaltFailed);
-
- /* open the sound input device */
- err = SPBOpenDevice (nil, siWritePermission, &SoundRefNum);
- if (err != noErr)
- return(kOpeningDevice);
- gGlobal->SoundRefNum = SoundRefNum;
-
- /* Get the sample rate information for the snd header */
- err = SPBGetDeviceInfo (SoundRefNum,siSampleRate, (Ptr) &SampleRate);
- if (err != noErr)
- return(kGettingRate);
-
-
- /* start allocating buffers (leave room for an extra buffer, and other mem) */
- /* first find out how many buffers we can allocate */
- NumBuffers = ((FreeMem() - kExtraMem) >>kBuffSizePower) - 1;
- if (NumBuffers < 1)
- return(kMemory);
-
- /* allocate the array of pointers */
- BuffArray = (BuffInfo*)NewPtr(sizeof(BuffInfo)*NumBuffers);
- if ((err = MemError()) != noErr || BuffArray == nil)
- return(kMemory);
- gGlobal->BuffArray = BuffArray;
- gGlobal->NumBuffers = NumBuffers;
-
- /* allocate the sound buffers, and initialize the BuffInfo structures */
- for (i=0; i<NumBuffers; i++)
- {
- BuffArray[i].buffer = SetUpSounds(BuffArray[i].buffer, &BuffArray[i].headerlength, SampleRate);
- if (BuffArray[i].buffer == nil)
- return(kBufSetup);
- BuffArray[i].buffSize = kBuffSize - BuffArray[i].headerlength;
- BuffArray[i].full = false;
- }
-
- /* build the RecordRec pointer and fill in the fields */
- RecordRec = (SPBPtr) NewPtr(sizeof (SPB));
- if ((err = MemError()) != noErr || RecordRec == nil)
- return(kMemory);
- gGlobal->RecordRec = RecordRec;
-
- RecordRec->inRefNum = SoundRefNum;
- RecordRec->count = kBuffSize - BuffArray[0].headerlength;
- RecordRec->milliseconds = 0;
- RecordRec->bufferLength = kBuffSize - BuffArray[0].headerlength;
- RecordRec->bufferPtr = (Ptr) ((*BuffArray[0].buffer) + BuffArray[0].headerlength);
- RecordRec->completionRoutine = (ProcPtr) &myRecordCallback;
- RecordRec->interruptRoutine = nil;
- RecordRec->userLong = (long)gGlobal;
- RecordRec->error = 0;
- RecordRec->unused1 = 0;
-
- /* allocate the array for the fft */
- gGlobal->fftData = (long double *)NewPtr(kSample*sizeof(long double));
- if ((err = MemError()) != noErr || gGlobal->fftData == nil)
- return(kMemory);
-
- /* finish setting up the global var */
- gGlobal->Error = kNoError;
- gGlobal->FullBuffer = false;
- gGlobal->RecordingBuff = -1;
- gGlobal->Quit = false;
- gGlobal->FirstTime = true;
-
- return(kNoError);
- }
-
-
- /****************************************/
- /* myRecordCallback:
- * function called when a buffer being recorded into becomes full
- * returns: an integer error code through the global
- */
- pascal void myRecordCallback (SPBPtr inParamPtr)
- {
- gGlobalType* CallbackGlobal;
-
- CallbackGlobal = (gGlobalType*)inParamPtr->userLong;
- CallbackGlobal->FullBuffer = true;
-
- if (inParamPtr->error != noErr && !CallbackGlobal->Quit)
- gGlobal->Error = kCallbackError;
- return;
- }
-
-
- /****************************************/
- /* finishToneRecognize:
- * clean up, and deallocate all of the memory
- * returns: the error number (defined in the .h file)
- */
- int finishToneRecognize()
- {
- int i;
- SndCommand mycmd;
-
- /* have we already cleaned up? */
- if (!gGlobal)
- return;
-
- if (gGlobal->SoundRefNum != 0)
- {
- /* stop recording and close the input device */
- if (SPBStopRecording(gGlobal->SoundRefNum) != noErr)
- return(kStopRecord);
- if (SPBCloseDevice(gGlobal->SoundRefNum) != noErr)
- return(kStopRecord);
- }
-
- /* deallocate the buffers */
- for (i = 0; i < gGlobal->NumBuffers; i++)
- if (gGlobal->BuffArray[i].buffer)
- {
- HUnlock(gGlobal->BuffArray[i].buffer);
- DisposeHandle(gGlobal->BuffArray[i].buffer);
- }
-
- /* deallocate the global structure */
- DisposePtr((Ptr) gGlobal->fftData);
- DisposePtr((Ptr) gGlobal->BuffArray);
- DisposePtr((Ptr) gGlobal->RecordRec);
- DisposePtr((Ptr) gGlobal);
- }
-
-
- /************************************/
- /* toneRecognize:
- * This routine is called using idle time, and records into the buffers
- * Then the Fast Fourier Transform (FFT) is run on the buffers to determine
- * What button was pressed on the phone.
- * By the nature of how we are processing the buffer's, the generated
- * numbers will be in sequence
- * returns: either a pointer to a linked list or an error number
- */
- long toneRecognize(XCmdPtr paramPtr)
- {
- int filledBuff,recordingBuff;
-
- /* if this is not the first time through and we don't have a full buffer exit */
- if (!gGlobal->FullBuffer && !gGlobal->FirstTime)
- return(kNoError);
-
- /* if we have a full buffer or this is the first time through then start recording */
- if (gGlobal->FullBuffer || gGlobal->FirstTime)
- {
- /* save which buffer got filled */
- filledBuff = gGlobal->RecordingBuff;
- if (filledBuff != -1)
- gGlobal->BuffArray[filledBuff].full = true;
-
- /* asynchronously start recording the next available buffer */
- recordingBuff = findAvailableBuff();
- if (recordingBuff == -1)
- return(kBuffersFull);
- gGlobal->RecordingBuff = recordingBuff;
- HUnlock(gGlobal->BuffArray[recordingBuff].buffer);
- SetHandleSize(gGlobal->BuffArray[recordingBuff].buffer,kBuffSize);
- HLockHi(gGlobal->BuffArray[recordingBuff].buffer);
- gGlobal->RecordRec->bufferPtr = (*(gGlobal->BuffArray[recordingBuff].buffer)) +
- gGlobal->BuffArray[recordingBuff].headerlength;
- gGlobal->FullBuffer = false;
- if (SPBRecord (gGlobal->RecordRec, true) != noErr)
- return(kRecord);
-
- /* if it isn't the first time, process this buffer */
- if (!gGlobal->FirstTime)
- {
- /* run the FFT on the non-quiet sections of the buffer and
- * pass back the sequence of numbers */
- ParseBuff(gGlobal->BuffArray[filledBuff],paramPtr);
-
- /* mark the buffer as recordable */
- gGlobal->BuffArray[filledBuff].full = false;
- }
- gGlobal->FirstTime = false;
- return(kNoError);
- }
- }
-
- /* ParseBuff:
- * a function to determine where the sounds occur and run the fft only on that
- * section
- * returns: one char number that was found */
- char ParseBuff(BuffInfo buffer,XCmdPtr paramPtr)
- {
- long i,j;
- int k = 0,size = kNumsInSample - 1;
- char num;
- unsigned char *tmpbuff,*tmpbuff2;
- long double *data;
-
- data = gGlobal->fftData;
- data[0] = 0.0;
-
- /* dereference the pointer */
- tmpbuff = *(buffer.buffer);
-
- /* start searching the buffer */
- for (i = buffer.headerlength; i < buffer.headerlength + buffer.buffSize; i++)
- {
- if (Abs(0x80 - (unsigned char)tmpbuff[i]) > kThreshold)/* if above threshold */
- {
- /* make sure that there are enough bytes to do a kSample segment */
- if ((i+kSampleDiv2) > (buffer.headerlength + buffer.buffSize))
- break; /* not enough */
- /* found beginning of a sound just take the next kSample bytes */
- for(k = 1; k< kSample; k++) {
- data[k++] = (long double) tmpbuff[i++]; /* real part */
- data[k] = 0.0; /* complex part */
- }
- /* i got incremented by sampleDiv2 size */
- /* fft the sound storing the resulting key */
- nfft(data,1024,1);
- num = findmax(data,1024,paramPtr);
- }
- }
- return num;
- }
-
-
- /*******************
- * FFT section
- * the numerical recipes fft #1
- * assumes that the isign is 1
- */
- #define SWAP(a,b) tempr=(a);(a)=(b);(b)=tempr
-
- /* replaces data by its discrete Fourier transform if isign=1
- * replaces data by nn times its inverse Fourier if isign=-1
- * data is a complex array of length nn, input as a real array[1..2*nn]
- * the fact that nn is an integer power of two is not checked */
- void nfft(long double data[],unsigned long nn,int isign)
- {
- unsigned long n,mmax,m,j,istep,i;
- long double wtemp,wr,wpr,wpi,wi,theta;
- long double tempr,tempi;
-
- n=nn <<1;
- j=1;
- for (i=1;i<n;i+=2) { /* bit reversal segment */
- if (j >i) {
- SWAP(data[j],data[i]); /* exchange the complex nums */
- SWAP(data[j+1],data[i+1]);
- }
- m=n >> 1;
- while (m >= 2 && j > m) {
- j -= m;
- m >>= 1;
- }
- j += m;
- }
- mmax=2; /* here begins the danielson-lanczos sec. */
- while (n > mmax) { /* oouter loop exec. log2 nn times */
- istep=2*mmax;
- theta=6.28318530717959/mmax; /* init for trig reoccurance (originally *isign) */
- wtemp=sin(0.5*theta);
- wpr = -2.0*wtemp*wtemp;
- wpi=sin(theta);
- wr=1.0;
- wi=0.0;
- for (m=1;m<mmax;m+=2) { /* here are 2 nested inner loops */
- for (i=m;i<=n;i+=istep) {
- j=i+mmax; /* this is the danielson-lanczos formula: */
- tempr=wr*data[j]-wi*data[j+1];
- tempi=wr*data[j+1]+wi*data[j];
- data[j]=data[i]-tempr;
- data[j+1]=data[i+1]-tempi;
- data[i] += tempr;
- data[i+1] += tempi;
- } /* trig recurrance */
- wr=(wtemp=wr)*wpr-wi*wpi+wr;
- wi=wi*wpr+wtemp*wpi+wi;
- }
- mmax=istep;
- }
- }
-
-
- /* find the row and column in the fourier-ed data, and return a char based on them */
- char findmax(long double Data[],unsigned long Samples,XCmdPtr paramPtr)
- {
- int row,col;
-
- /* determine the row */
- if (sqrt(Data[R1fft*2-1]*Data[R1fft*2-1] + Data[R1fft*2]*Data[R1fft*2]) > kFFTthreshold)
- row =1;
- else if (sqrt(Data[R2fft*2-1]*Data[R2fft*2-1] + Data[R2fft*2]*Data[R2fft*2]) > kFFTthreshold)
- row = 2;
- else if (sqrt(Data[R3fft*2-1]*Data[R3fft*2-1] + Data[R3fft*2]*Data[R3fft*2]) > kFFTthreshold)
- row = 3;
- else if (sqrt(Data[R4fft*2-1]*Data[R4fft*2-1] + Data[R4fft*2]*Data[R4fft*2]) > kFFTthreshold)
- row = 4;
- else return kBadKey;
-
- /* determine the column */
- if (sqrt(Data[C1fft*2-1]*Data[C1fft*2-1] + Data[C1fft*2]*Data[C1fft*2]) > kFFTthreshold)
- col =1;
- else if (sqrt(Data[C2fft*2-1]*Data[C2fft*2-1] + Data[C2fft*2]*Data[C2fft*2]) > kFFTthreshold)
- col = 2;
- else if (sqrt(Data[C3fft*2-1]*Data[C3fft*2-1] + Data[C3fft*2]*Data[C3fft*2]) > kFFTthreshold)
- col = 3;
- else return kBadKey;
-
- /* invoke the proper handler */
- switch (row) {
- case 1: /* 1 2 3 */
- RunHandler(paramPtr,phScripts[col]);
- return keypad[row][col];
- break;
- case 2: /* 4 5 6 */
- RunHandler(paramPtr,phScripts[col+3]);
- return keypad[row][col];
- break;
- case 3: /* 7 8 9 */
- RunHandler(paramPtr,phScripts[col+6]);
- return keypad[row][col];
- break;
- case 4: /* # 0 * */
- switch (col) {
- case 1:
- RunHandler(paramPtr,phScripts[10]);
- return '*';
- case 2:
- RunHandler(paramPtr,phScripts[0]);
- return '0';
- case 3:
- RunHandler(paramPtr,phScripts[11]);
- return '#';
- }
- break;
- }
- return kBadKey;
- }